{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9f955c17",
   "metadata": {},
   "source": [
    "# 原则四：接口分离原则（Interface Segregation Principle）\n",
    "\n",
    "##定义\n",
    "- Many client specific interfaces are better than one general purpose interface.\n",
    "\n",
    "即：多个特定的客户端接口要好于一个通用性的总接口。\n",
    "\n",
    "\n",
    "定义解读\n",
    "- 客户端不应该依赖它不需要实现的接口。\n",
    "- 不建立庞大臃肿的接口，应尽量细化接口，接口中的方法应该尽量少。\n",
    "- 需要注意的是：接口的粒度也不能太小。如果过小，则会造成接口数量过多，使设计复杂化。\n",
    "\n",
    "## 优点\n",
    "避免同一个接口里面包含不同类职责的方法，接口责任划分更加明确，符合高内聚低耦合的思想。\n",
    "\n",
    "## 代码讲解\n",
    "下面通过一个餐厅服务的例子讲解一下接口分离原则。\n",
    "\n",
    "需求点\n",
    "\n",
    "现在的餐厅除了提供传统的店内服务，多数也都支持网上下单，网上支付功能。写一些接口方法来涵盖餐厅的所有的下单及支付功能。\n",
    "\n",
    "### 不好的设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "de22842e",
   "metadata": {},
   "outputs": [],
   "source": [
    "class RestaurantProtocol(object):\n",
    "    def placeOnlineOrder(self):  # 下订单：online\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "        \n",
    "    def placeTelephoneOrder(self):  # 下订单：通过电话\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "    \n",
    "    def placeWalkInCustomerOrder(self):  # 下订单：在店里\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "    \n",
    "    def payOnline(self):  # 支付订单：online\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "        \n",
    "    def payInPerson(self):  # 支付订单：在店里支付\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86fd74ea",
   "metadata": {},
   "source": [
    "在这里声明了一个接口，它包含了下单和支付的几种方式：\n",
    "\n",
    "- 下单：\n",
    "    - online下单\n",
    "    - 电话下单\n",
    "    - 店里下单（店内服务）\n",
    "\n",
    "- 支付\n",
    "    - online支付（适用于online下单和电话下单的顾客）\n",
    "    - 店里支付（店内服务）\n",
    "\n",
    "这里先不讨论电话下单的顾客是用online支付还是店内支付。\n",
    "\n",
    "对应的，我们有三种下单方式的顾客：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "06a09d8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1.online下单，online支付的顾客\n",
    "\n",
    "class OnlineClient(RestaurantProtocol):\n",
    "    def placeOnlineOrder(self):  # 下订单：online\n",
    "        print(f\"place on line order\") \n",
    "        \n",
    "    def placeTelephoneOrder(self):  # 下订单：通过电话\n",
    "        pass\n",
    "    \n",
    "    def placeWalkInCustomerOrder(self):  # 下订单：在店里\n",
    "        pass\n",
    "    \n",
    "    def payOnline(self):  # 支付订单：online\n",
    "        print(f\"pay on line\") \n",
    "        \n",
    "    def payInPerson(self):  # 支付订单：在店里支付\n",
    "        pass\n",
    "    \n",
    "# 2.电话下单，online支付的顾客\n",
    "class TelephoneClient(RestaurantProtocol):\n",
    "    def placeOnlineOrder(self):  # 下订单：online\n",
    "        pass\n",
    "        \n",
    "    def placeTelephoneOrder(self):  # 下订单：通过电话\n",
    "        print(\"place telephone order\")\n",
    "    \n",
    "    def placeWalkInCustomerOrder(self):  # 下订单：在店里\n",
    "        pass\n",
    "    \n",
    "    def payOnline(self):  # 支付订单：online\n",
    "        print(f\"pay on line\") \n",
    "        \n",
    "    def payInPerson(self):  # 支付订单：在店里支付\n",
    "        pass\n",
    "    \n",
    "# 3.在店里下单并支付的顾客：\n",
    "class WalkinClient(RestaurantProtocol):\n",
    "    def placeOnlineOrder(self):  # 下订单：online\n",
    "        pass\n",
    "        \n",
    "    def placeTelephoneOrder(self):  # 下订单：通过电话\n",
    "        pass\n",
    "    \n",
    "    def placeWalkInCustomerOrder(self):  # 下订单：在店里\n",
    "        print(\"place walk in customer order\")\n",
    "    \n",
    "    def payOnline(self):  # 支付订单：online\n",
    "        pass\n",
    "        \n",
    "    def payInPerson(self):  # 支付订单：在店里支付\n",
    "        print(\"pay in person\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6223a2ef",
   "metadata": {},
   "source": [
    "我们发现，并不是所有顾客都必须要实现RestaurantProtocol里面的所有方法。由于接口方法的设计造成了冗余，因此该设计不符合接口隔离原则。\n",
    "\n",
    "注意，Objective-C中的协议可以通过@optional关键字设置不需要必须实现的方法，该特性不与接口分离原则冲突：只要属于同一类责任的接口，都可以放入同一接口中。\n",
    "\n",
    "那么如何做才符合接口隔离原则呢？我们来看一下较好的设计。\n",
    "\n",
    "## 较好的设计\n",
    "\n",
    "要符合接口隔离原则，只需要将不同类型的接口分离出来即可。我们将原来的RestaurantProtocol接口拆分成两个接口：下单接口和支付接口。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a942648f",
   "metadata": {},
   "outputs": [],
   "source": [
    "class RestaurantPlaceOrderProtocol(object):\n",
    "    def placeOrder(self):  # 下订单\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "        \n",
    "class RestaurantPaymentProtocol(object):\n",
    "    def payOrder(self):  # 付款\n",
    "        print(f\"子类必须实现 方法\")\n",
    "        raise NotImplementedError\n",
    "        \n",
    "# 接着另online下单，电话下单，店内下单的顾客继承这个父类，分别实现这两个接口的方法：\n",
    "# 1.online下单，online支付的顾客\n",
    "class OnlineClient(RestaurantPlaceOrderProtocol, RestaurantPaymentProtocol):\n",
    "    def placeOrder(self):   \n",
    "        print(f\"place on line order\") \n",
    "        \n",
    "    def payOrder(self):    \n",
    "        print(f\"pay on line\") \n",
    "        \n",
    "# 2.电话下单，online支付的顾客\n",
    "class TelephoneClient(RestaurantPlaceOrderProtocol, RestaurantPaymentProtocol):\n",
    "    def placeOrder(self):   \n",
    "        print(f\"place on telephone order\") \n",
    "        \n",
    "    def payOrder(self):    \n",
    "        print(f\"pay on line\") \n",
    "\n",
    "# 3.在店里下单并支付顾客：\n",
    "class WalkinClient(RestaurantPlaceOrderProtocol, RestaurantPaymentProtocol):\n",
    "    def placeOrder(self):   \n",
    "        print(f\"place on customer order\") \n",
    "        \n",
    "    def payOrder(self):    \n",
    "        print(f\"pay in person\") "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d88476ba",
   "metadata": {},
   "source": [
    "因为我们把不同职责的接口拆开，使得接口的责任更加清晰，简洁明了。不同的客户端可以根据自己的需求遵循所需要的接口来以自己的方式实现。\n",
    "\n",
    "而且今后如果还有和下单或者支付相关的方法，也可以分别加入到各自的接口中，避免了接口的臃肿，同时也提高了程序的内聚性。\n",
    "\n",
    "下面来看一下这两个设计的UML 类图，可以更形象地看出两种设计上的区别：\n",
    "\n",
    "UML 类图对比\n",
    "\n",
    "未实践接口分离原则：\n",
    "\n",
    "![101](pic/104-101.png)\n",
    "\n",
    "实践了接口分离原则：\n",
    "![101](pic/104-102.png)\n",
    "\n",
    "如何实践\n",
    "在设计接口时，尤其是在向现有的接口添加方法时，我们需要仔细斟酌这些方法是否是处理同一类任务的：如果是则可以放在一起；如果不是则需要做拆分。\n",
    "\n",
    "做iOS开发的朋友对UITableView的UITableViewDelegate和UITableViewDataSource这两个协议应该会非常熟悉。这两个协议里的方法都是与UITableView相关的，但iOS SDK的设计者却把这些方法放在不同的两个协议中。原因就是这两个协议所包含的方法所处理的任务是不同的两种：\n",
    "\n",
    "UITableViewDelegate：含有的方法是UITableView的实例告知其代理一些点击事件的方法，即事件的传递，方向是从UITableView的实例到其代理。\n",
    "UITableViewDataSource：含有的方法是UITableView的代理传给UITableView一些必要数据供UITableView展示出来，即数据的传递，方向是从UITableView的代理到UITableView。\n",
    "很显然，UITableView协议的设计者很好地实践了接口分离的原则，值得我们大家学习。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b7a6c05",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03809259",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45a1d68c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf202fcc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "453bc715",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
